/*
 * UAE - The Un*x Amiga Emulator
 *
 * MC68000 emulation
 *
 * Copyright 1995 Bernd Schmidt
 */

#pragma once

#include "events.h"
#include "memory.h"

#ifndef SET_CFLG

    #define SET_CFLG(x) (CFLG() = (x))
    #define SET_NFLG(x) (NFLG() = (x))
    #define SET_VFLG(x) (VFLG() = (x))
    #define SET_ZFLG(x) (ZFLG() = (x))
    #define SET_XFLG(x) (XFLG() = (x))

    #define GET_CFLG() CFLG()
    #define GET_NFLG() NFLG()
    #define GET_VFLG() VFLG()
    #define GET_ZFLG() ZFLG()
    #define GET_XFLG() XFLG()

    #define CLEAR_CZNV() do { \
        SET_CFLG(0); \
        SET_ZFLG(0); \
        SET_NFLG(0); \
        SET_VFLG(0); \
} while (0)

    #define COPY_CARRY() (SET_XFLG(GET_CFLG()))
#endif

extern const int areg_byteinc[];
extern const int imm8_table[];

extern int movem_index1[256];
extern int movem_index2[256];
extern int movem_next[256];

#ifdef FPUEMU
extern int fpp_movem_index1[256];
extern int fpp_movem_index2[256];
extern int fpp_movem_next[256];
#endif

typedef uint REGPARAM3 cpuop_func (uint) REGPARAM;
typedef void REGPARAM3 cpuop_func_ce (uint) REGPARAM;

struct cputbl
{
    cpuop_func* handler;
    ushort opcode;
};

#ifdef JIT
typedef uint REGPARAM3 compop_func (uint) REGPARAM;

struct comptbl
{
    compop_func* handler;
    uint opcode;
    int specific;
};
#endif

extern uint REGPARAM3 op_illg(uint) REGPARAM;

typedef byte flagtype;

#ifdef FPUEMU
/* You can set this to long double to be more accurate. However, the
   resulting alignment issues will cost a lot of performance in some
   apps */
    #define USE_LONG_DOUBLE 0

    #if USE_LONG_DOUBLE
typedef long double fptype;
        #define LDPTR tbyte ptr
    #else
typedef double fptype;
        #define LDPTR qword ptr
    #endif
#endif

#define MAX68020CYCLES 4

#define CPU_PIPELINE_MAX 3
#define CPU000_MEM_CYCLE 4
#define CPU000_CLOCK_MULT 2
#define CPU020_MEM_CYCLE 3
#define CPU020_CLOCK_MULT 4

#define CACHELINES020 64
struct cache020
{
    uint data;
    uint tag;
    bool valid;
};

#define CACHELINES030 16
struct cache030
{
    uint data[4];
    bool valid[4];
    uint tag;
};

#define CACHESETS040 64
#define CACHELINES040 4
struct cache040
{
    uint data[CACHELINES040][4];
    bool valid[CACHELINES040];
    uint tag[CACHELINES040];
};

struct regstruct
{
    uint regs[16];

    uint pc;
    byte* pc_p;
    byte* pc_oldp;
    uint instruction_pc;

    ushort irc, ir;
    uint spcflags;

    uae_ptr usp, isp, msp;
    ushort sr;
    flagtype t1;
    flagtype t0;
    flagtype s;
    flagtype m;
    flagtype x;
    flagtype stopped;
    int exception;
    int intmask;
    int ipl, ipl_pin;

    uint vbr, sfc, dfc;

    #ifdef FPUEMU
    fptype fp[8];
    fptype fp_result;

    uint fpcr, fpsr, fpiar;
    uint fpsr_highbyte;
    #endif
    #ifndef CPUEMU_68000_ONLY
    uint cacr, caar;
    uint itt0, itt1, dtt0, dtt1;
    uint tcr, mmusr, urp, srp, buscr;
    uint mmu_fslw, mmu_fault_addr;
    ushort mmu_ssw;
    uint wb3_data;
    ushort wb3_status;
    int mmu_enabled;
    int mmu_pagesize_8k;
    #endif

    uint pcr;
    uint address_space_mask;

    byte panic;
    uint panic_pc, panic_addr;

    ushort prefetch020[CPU_PIPELINE_MAX];
    uint prefetch020addr;
    uint cacheholdingdata020;
    uint cacheholdingaddr020;
    int ce020memcycles;
    int ce020tmpcycles;
};

extern struct regstruct regs;

#define MAX_CPUTRACESIZE 128
struct cputracememory
{
    uint addr;
    uint data;
    int mode;
};

struct cputracestruct
{
    uint regs[16];
    uint usp, isp, pc;
    ushort ir, irc, sr, opcode;
    int intmask, stopped, state;

    uint msp, vbr;
    uint cacr, caar;
    ushort prefetch020[CPU_PIPELINE_MAX];
    uint prefetch020addr;
    uint cacheholdingdata020;
    uint cacheholdingaddr020;
    struct cache020 caches020[CACHELINES020];

    uint startcycles;
    int needendcycles;
    int memoryoffset;
    int cyclecounter, cyclecounter_pre, cyclecounter_post;
    int readcounter, writecounter;
    struct cputracememory ctm[MAX_CPUTRACESIZE];
};

static __forceinline uint munge24(uint x)
{
    return x & regs.address_space_mask;
}

extern int mmu_enabled, mmu_triggered;
extern int cpu_cycles;
extern int cpucycleunit;
static __forceinline void set_special(uint x)
{
    regs.spcflags |= x;
    cycles_do_special();
}

static __forceinline void unset_special(uint x)
{
    regs.spcflags &= ~x;
}

#define m68k_dreg(r, num) ((r).regs[(num)])
#define m68k_areg(r, num) (((r).regs + 8)[(num)])

static __forceinline void m68k_setpc(uae_ptr newpc)
{
    regs.pc_p = regs.pc_oldp = get_real_address(newpc);
    regs.instruction_pc = regs.pc = newpc;
}

static __forceinline uae_ptr m68k_getpc()
{
    return (uae_ptr)(regs.pc + ((byte*)regs.pc_p - (byte*)regs.pc_oldp));
}
#define M68K_GETPC m68k_getpc()

static __forceinline uae_ptr m68k_getpc_p(byte* p)
{
    return (uae_ptr)(regs.pc + ((byte*)p - (byte*)regs.pc_oldp));
}

static __forceinline void m68k_incpc(int o)
{
    regs.pc_p += o;
}

static __forceinline void m68k_setpc_mmu(uae_ptr newpc)
{
    regs.instruction_pc = regs.pc = newpc;
    regs.pc_p = regs.pc_oldp = 0;
}
static __forceinline void m68k_setpci(uae_ptr newpc)
{
    regs.instruction_pc = regs.pc = newpc;
}
static __forceinline uae_ptr m68k_getpci()
{
    return regs.pc;
}
static __forceinline void m68k_incpci(int o)
{
    regs.pc += o;
}

static __forceinline void m68k_do_rts()
{
    uint newpc = get_long(m68k_areg(regs, 7));
    m68k_setpc(newpc);
    m68k_areg(regs, 7) += 4;
}

static __forceinline void m68k_do_bsr(uae_ptr oldpc, int offset)
{
    m68k_areg(regs, 7) -= 4;
    put_long(m68k_areg(regs, 7), oldpc);
    m68k_incpc(offset);
}

static __forceinline uint get_ibyte(int o)
{
    return do_get_mem_byte((byte*)((regs).pc_p + (o) + 1));
}
static __forceinline uint get_iword(int o)
{
    return do_get_mem_word((ushort*)((regs).pc_p + (o)));
}
static __forceinline uint get_ilong(int o)
{
    return do_get_mem_long((uint*)((regs).pc_p + (o)));
}

#define get_iwordi(o) get_wordi(o)
#define get_ilongi(o) get_longi(o)

/* These are only used by the 68020/68881 code, and therefore don't
 * need to handle prefetch.  */
static __forceinline uint next_ibyte()
{
    uint r = get_ibyte(0);
    m68k_incpc(2);
    return r;
}
static __forceinline uint next_iword()
{
    uint r = get_iword(0);
    m68k_incpc(2);
    return r;
}
static __forceinline uint next_iwordi()
{
    uint r = get_iwordi(m68k_getpci());
    m68k_incpc(2);
    return r;
}
static __forceinline uint next_ilong()
{
    uint r = get_ilong(0);
    m68k_incpc(4);
    return r;
}
static __forceinline uint next_ilongi()
{
    uint r = get_ilongi(m68k_getpci());
    m68k_incpc(4);
    return r;
}

extern uint (* x_prefetch)(int);
extern uint (* x_prefetch_long)(int);
extern uint (* x_get_byte)(uae_ptr addr);
extern uint (* x_get_word)(uae_ptr addr);
extern uint (* x_get_long)(uae_ptr addr);
extern void (* x_put_byte)(uae_ptr addr, uint v);
extern void (* x_put_word)(uae_ptr addr, uint v);
extern void (* x_put_long)(uae_ptr addr, uint v);
extern uint (* x_next_iword)();
extern uint (* x_next_ilong)();
extern uint (* x_get_ilong)(int);
extern uint (* x_get_iword)(int);
extern uint (* x_get_ibyte)(int);
extern void (* x_do_cycles)(uint);
extern void (* x_do_cycles_pre)(uint);
extern void (* x_do_cycles_post)(uint, uint);

extern uint REGPARAM3 x_get_disp_ea_020(uint base, uint dp) REGPARAM;
extern uint REGPARAM3 x_get_bitfield(uint src, uint bdata[2], int offset, int width) REGPARAM;
extern void REGPARAM3 x_put_bitfield(uint dst, uint bdata[2], uint val, int offset, int width) REGPARAM;

extern void m68k_setstopped();
extern void m68k_resumestopped();

extern uint REGPARAM3 get_disp_ea_020(uint base, uint dp) REGPARAM;
extern uint REGPARAM3 get_bitfield(uint src, uint bdata[2], int offset, int width) REGPARAM;
extern void REGPARAM3 put_bitfield(uint dst, uint bdata[2], uint val, int offset, int width) REGPARAM;

extern void m68k_disasm_ea(void* f, uae_ptr addr, uae_ptr* nextpc, int cnt, uint* seaddr, uint* deaddr);
extern void m68k_disasm(void* f, uae_ptr addr, uae_ptr* nextpc, int cnt);
extern void m68k_disasm_2(TCHAR* buf, int bufsize, uae_ptr addr, uae_ptr* nextpc, int cnt, uint* seaddr, uint* deaddr, int safemode);
extern int get_cpu_model();

extern void REGPARAM3 MakeSR() REGPARAM;
extern void REGPARAM3 MakeFromSR() REGPARAM;
extern void REGPARAM3 Exception(int) REGPARAM;
extern void NMI();
extern void NMI_delayed();
extern void prepare_interrupt(uint);
extern void doint();
extern void dump_counts();
extern int m68k_move2c(int, uint*);
extern int m68k_movec2(int, uint*);
extern void m68k_divl(uint, uint, ushort);
extern void m68k_mull(uint, uint, ushort);
extern void init_m68k();
extern void init_m68k_full();
extern void m68k_go(int);
extern void m68k_dumpstate(void*, uae_ptr*);
extern void m68k_disasm(void*, uae_ptr, uae_ptr *, int);
extern void sm68k_disasm(TCHAR*, TCHAR*, uae_ptr addr, uae_ptr* nextpc);
extern void m68k_reset(int);
extern int getDivu68kCycles(uint dividend, ushort divisor);
extern int getDivs68kCycles(int dividend, short divisor);
extern void m68k_do_rte();

extern void mmu_op(uint, uint);
extern void mmu_op30(uae_ptr, uint, ushort, uae_ptr);

extern void fpuop_arithmetic(uint, ushort);
extern void fpuop_dbcc(uint, ushort);
extern void fpuop_scc(uint, ushort);
extern void fpuop_trapcc(uint, uae_ptr, ushort);
extern void fpuop_bcc(uint, uae_ptr, uint);
extern void fpuop_save(uint);
extern void fpuop_restore(uint);
extern uint fpp_get_fpsr();
extern void fpu_reset();
extern void fpux_save(int*);
extern void fpux_restore(int*);

extern void exception3(uint opcode, uae_ptr addr);
extern void exception3i(uint opcode, uae_ptr addr);
extern void exception2(uae_ptr addr);
extern void cpureset();

extern void fill_prefetch();

#define CPU_OP_NAME(a) op ## a

/* 68060 */
extern const struct cputbl op_smalltbl_0_ff[];
extern const struct cputbl op_smalltbl_20_ff[]; // CE
/* 68040 */
extern const struct cputbl op_smalltbl_1_ff[];
extern const struct cputbl op_smalltbl_21_ff[]; // CE
extern const struct cputbl op_smalltbl_31_ff[]; // MMU
/* 68030 */
extern const struct cputbl op_smalltbl_2_ff[];
extern const struct cputbl op_smalltbl_22_ff[]; // CE
/* 68020 */
extern const struct cputbl op_smalltbl_3_ff[];
extern const struct cputbl op_smalltbl_23_ff[]; // CE
/* 68010 */
extern const struct cputbl op_smalltbl_4_ff[];
/* 68000 */
extern const struct cputbl op_smalltbl_5_ff[];
/* 68000 slow but compatible.  */
extern const struct cputbl op_smalltbl_11_ff[];
/* 68000 slow but compatible and cycle-exact.  */
extern const struct cputbl op_smalltbl_12_ff[];

extern cpuop_func* cpufunctbl[65536];

#ifdef JIT
extern void flush_icache(uae_ptr, int);
extern void compemu_reset();
extern bool check_prefs_changed_comp();
#else
    #define flush_icache(uae_ptr, int) do { } while (0)
#endif
extern void flush_dcache(uae_ptr, int);
extern void flush_mmu(uae_ptr, int);

extern int movec_illg(int regno);
extern uint val_move2c(int regno);
extern void val_move2c2(int regno, uint val);
struct cpum2c
{
    int regno;
    TCHAR* regname;
};
extern struct cpum2c m2cregs[];

extern bool is_cpu_tracer();
extern bool set_cpu_tracer(bool force);
extern bool can_cpu_tracer();